﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides the approximation of the second derivative by using extrapolation.
    /// </summary>
    [Serializable]
    public class RealSecondDerivativeExtrapolationApproximator
    {
        /// <summary>
        /// Internal array for extrapolation.
        /// </summary>
        private readonly double[] STANDARD_H = {1e-1, 1e-2, 1e-3, 1e-4, 1e-5};

        /// <summary>
        /// Internal array for extrapolation.
        /// </summary>
        private readonly double[] STANDARD_H_SQUARE = {1e-2, 1e-4, 1e-6, 1e-8, 1e-10};

        /// <summary>
        /// The source function for the to approximate second derivative.
        /// </summary>
        private IRealFunction sourceFunction;

        /// <summary>
        /// Initializes a new instance of the <see cref="RealSecondDerivativeExtrapolationApproximator"/> class.
        /// </summary>
        /// <param name="sourceFunction">The source function for the to approximate second derivative.</param>
        public RealSecondDerivativeExtrapolationApproximator(IRealFunction sourceFunction)
        {
            this.sourceFunction = sourceFunction;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RealSecondDerivativeExtrapolationApproximator"/> class.
        /// </summary>
        /// <param name="sourceFunction">The source function for the to approximate second derivative.</param>
        public RealSecondDerivativeExtrapolationApproximator(Polynomial sourceFunction)
        {
            this.sourceFunction = sourceFunction;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RealSecondDerivativeExtrapolationApproximator"/> class.
        /// </summary>
        /// <param name="sourceFunction">The source function for the to approximate second derivative.</param>
        public RealSecondDerivativeExtrapolationApproximator(SimplePolynomial sourceFunction)
        {
            this.sourceFunction = sourceFunction;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RealSecondDerivativeExtrapolationApproximator"/> class.
        /// </summary>
        /// <param name="sourceFunction">The source function for the to approximate second derivative.</param>
        public RealSecondDerivativeExtrapolationApproximator(SimpleRationalPolynomial sourceFunction)
        {
            if (sourceFunction == (SimpleRationalPolynomial) null)
            {
                throw new ArgumentNullException("sourceFunction");
            }

            this.sourceFunction = sourceFunction;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RealSecondDerivativeExtrapolationApproximator"/> class.
        /// </summary>
        /// <param name="sourceFunction">The source function for the to approximate second derivative.</param>
        public RealSecondDerivativeExtrapolationApproximator(HardRealFunction sourceFunction)
        {
            this.sourceFunction = sourceFunction;
        }

        /// <summary>
        /// Gets or sets the source function for the to approximate derivative.
        /// </summary>
        /// <value>The source function for the to approximate derivative.</value>
        public IRealFunction SourceFunction
        {
            get { return sourceFunction; }
            set { sourceFunction = value; }
        }

        /// <summary>
        /// Approximates the specified real function derivative by using extrapolation.
        /// </summary>
        /// <param name="x">The x at which the function derivative should be calculate.</param>
        /// <returns>The specified slope at the position x.</returns>
        public double ApproximateDerivative(double x)
        {
            double[] f1h = new double[5];

            for (int i = 0; i < 5; i++)
            {
                f1h[i] = (this.sourceFunction.SolveAt(x + this.STANDARD_H[i]) - 2*this.sourceFunction.SolveAt(x)
                          + this.sourceFunction.SolveAt(x - this.STANDARD_H[i]))/(this.STANDARD_H_SQUARE[i]);
            }

            return NevillePolynomialInterpolation.NevilleInterpolation(5, this.STANDARD_H_SQUARE, f1h);
        }
    }
}